use core::slice;

use cmsis_rtos2_rs::{osWaitForever, static_mem::{mq_size, EventFlags, MessageQueue}};
use periph_utils::{DmaConfig, GpioConfig, UartInit};
use periph_utils::stm32f1;
use periph_utils::stm32f1::stm32f103::NVIC;
use periph_utils::stm32f1::stm32f103::{Interrupt, interrupt};
use utils::{ByteBuffer, MutCell, VolatileHandle};

use crate::app::cargo::CargoErr;

#[derive(GpioConfig, DmaConfig, UartInit)]
#[gpio(
    init = "gpio_init",
    io(name = "tx", io = "PA9", mode = "AF_PP"),
    io(name = "rx", io = "PA10", mode = "IN_FLOAT")
)]
#[dma(
    init = "dma_init",
    ch(name = "usart1_tx", dma = 1, ch = 4, size = 8, dir = "write", tcie),
    ch(name = "usart1_rx", dma = 1, ch = 5, size = 8, dir = "read", circ)
)]
#[uart_config(uart = 1, size = 8, baud_rate = 9600)]
struct Pin;

const MSG_COUNT: usize = 2;

struct Context {
    flags: EventFlags,
    mq: MessageQueue<&'static [u8], MSG_COUNT, { mq_size::<&'static [u8]>(MSG_COUNT) }>,
    recv_buf: ByteBuffer<255>,
    recv_index: MutCell<usize>,
}
const FLAG_TC: u32 = 0x01;

static CTX: Context = Context::uninit();

const SCAN_BYTES: [u8; 2] = [0x1B, 0x31];

impl Context {
    const fn uninit() -> Self {
        Self {
            flags: EventFlags::uninit(),
            mq: MessageQueue::uninit(),
            recv_buf: ByteBuffer::const_new(),
            recv_index: MutCell::const_new(0),
        }
    }

    unsafe fn init_once(&self) {
        self.flags.init_once("scan_uart");
        self.mq.init_once("scan_uart");
    }
}

pub unsafe fn init() {
    CTX.init_once();
    
    Pin::gpio_init();
    Pin::dma_init();
    Pin::usart1_init();

    NVIC::unmask(Interrupt::USART1);
    NVIC::unmask(Interrupt::DMA1_CHANNEL4);
}

pub fn scan() -> Result<&'static [u8], CargoErr> {
    
    CTX.mq.reset();
    unsafe { CTX.recv_index.as_mut().volatile_write(0); }

    Pin::usart1_rx_dma1_ch5_read(
        CTX.recv_buf.as_ptr() as u32,
        USART1.dr.as_ptr() as u32,
        CTX.recv_buf.size() as u16,
    );

    Pin::usart1_tx_dma1_ch4_write(
        SCAN_BYTES.as_ptr() as u32,
        USART1.dr.as_ptr() as u32,
        SCAN_BYTES.len() as u16,
    );
    _ = CTX.flags.wait_any_flags(FLAG_TC, osWaitForever);

    if let Err(_) = CTX.mq.poll(500) {
        return Err(CargoErr::ScanTimeout);
    };

    let Ok(value) = CTX.mq.poll(3000) else {
        return Err(CargoErr::ScanTimeout);
    };

    if value.len() < 2 {
        return Err(CargoErr::ScanInvalid);
    }
    let v2 = &value[..(value.len() - 2)];

    Ok(v2)
}

unsafe fn recv_idle() {
    let index = CTX.recv_buf.size() - Pin::usart1_rx_dma1_ch5_ndtr() as usize;
    let last_index = CTX.recv_index.as_mut().volatile_read();

    if index == last_index {
        return;
    }
    let bw = slice::from_raw_parts(
        CTX.recv_buf.as_ptr().add(last_index), 
        index - last_index
    );
    _ = CTX.mq.post(&bw);
    CTX.recv_index.as_mut().volatile_write(index);
}

#[interrupt]
unsafe fn USART1() {
    if Pin::usart1_is_idle() {
        Pin::usart1_clear_idle();
        recv_idle();
    }
}

#[interrupt]
unsafe fn DMA1_CHANNEL4() {
    if Pin::usart1_tx_dma1_ch4_is_tc() {
        Pin::usart1_tx_dma1_ch4_clear_flags();
        _ = CTX.flags.set_flags(FLAG_TC);
    }
}
